#include <stdio.h>
#include <string.h>
#include <pwd.h>
#include <sys/types.h>
#include<stdlib.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h> 
#include <errno.h>
#include <pwd.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h> 

#define MAXLEN 1000
#define MAXNUM 100

#define BACKGROUND 				1
#define IN_REDIRECT 			2
#define OUT_REDIRECT 			4
#define OUT_REDIRECT_APPEND		8
#define IS_PIPED 				16

struct parse_info;
struct passwd *pwd;
char *buffer;

struct parse_info 
{
    int flag;
    char* in_file;
    char* out_file;
    char* command2;
    char** parameters2;
};


/*
	output the prompt for user
*/
void typePrompt(char *prompt)
{
	char hostname[MAXLEN];
	char pathname[MAXLEN];

	pwd = getpwuid(getuid());
	getcwd(pathname, MAXLEN);
	gethostname(hostname,MAXLEN);
	hostname[strlen("localhost")] = '\0';

	sprintf(prompt,"[[xShell] %s@%s %s]$ ", pwd->pw_name, hostname, pathname);
}

/*
	split user's input to command + parameters
*/
int getCmdPara(char *buffer, char **command, char **para)
{
	if(buffer[0] == '\0')
		return -1;

	char *pStart,*pEnd;
	int count = 0;
	int isFinished = 0;
	pStart = pEnd = buffer;
	while(isFinished == 0)
	{
		while((*pEnd == ' ' && *pStart == ' ') || (*pEnd == '\t' && *pStart == '\t'))
		{
			pStart++;
			pEnd++;
		}

		if(*pEnd == '\0' || *pEnd == '\n')
		{
			if(count == 0)
				return -1;
			break;
		}

		while(*pEnd != ' ' && *pEnd != '\0' && *pEnd != '\n')
			pEnd++;


		if(count == 0)
		{
			char *p = pEnd;
			*command = pStart;
			while(p!=pStart && *p !='/')
				p--;
			if(*p == '/')
				p++;
			para[0] = p;
			count += 2;
		}
		else if(count <= MAXLEN)
		{
			para[count-1] = pStart;
			count++;
		}
		else
		{
			break;
		}

		if(*pEnd == '\0' || *pEnd == '\n')
		{
			*pEnd = '\0';
			isFinished = 1;
		}
		else
		{
			*pEnd = '\0';
			pEnd++;
			pStart = pEnd;
		}
	}

	para[count-1] = NULL;

	return count;
}

/*
	return 0 represents not builtInCmd, 1 for yes
	cd command changes the current directory
	quit/exit for exit the shell 
*/
int builtInCmd(char *cmd, char **para)
{
	if (cmd == NULL) return 1;
	if (strcmp(cmd, "exit")==0 || strcmp(cmd, "quit")==0)
	{
		exit(0);
	}
	else if (strcmp(cmd, "cd")==0)
	{
		char *cd_path = NULL;

		if(para[1] == NULL)
		{
			para[1] = malloc(2 * sizeof(char));
			para[1][0]= '~';
			para[1][1]= '\0';
		}
		if(para[1][0] == '~')
		{
			cd_path = malloc(strlen(pwd->pw_dir)+strlen(para[1]));
			if(cd_path == NULL)
			{
				printf("cd:malloc failed.\n");
			}
			strcpy(cd_path,pwd->pw_dir);
			strncpy(cd_path+strlen(pwd->pw_dir),para[1]+1,strlen(para[1]));
		}
		else
		{
			cd_path = malloc(strlen(para[1]+1));
			if(cd_path == NULL)
			{
				printf("cd:malloc failed.\n");
			}
			strcpy(cd_path,para[1]);
		}
		if(chdir(cd_path)!= 0)
			printf("change directory error!\n");
		free(cd_path);
	}
	else
	{
		return 0;
	}
	return 1;
}


int parse_info_init(struct parse_info *info)
{
    info->flag = 0;
    info->in_file = NULL;
    info->out_file = NULL;
    info->command2 = NULL;
    info->parameters2 = NULL;
    return 0;
}

int parsing(char **parameters,int ParaNum,struct parse_info *info)
{
    int i;
    parse_info_init(info);
    if (ParaNum < 1)
    {
	return 1;
    }
    if(strcmp(parameters[ParaNum-1],"&") ==0)
    {
        info->flag |= BACKGROUND;
        parameters[ParaNum-1] = NULL;
        ParaNum--;
    }
    for(i=0;i<ParaNum;)
    {
        if(strcmp(parameters[i],"<<")==0 || strcmp(parameters[i],"<")==0)
        {
            info->flag |= IN_REDIRECT;
            info->in_file = parameters[i+1];
            parameters[i] = NULL;
            i+=2;
        }
        else if(strcmp(parameters[i],">")==0)
        {
            info->flag |= OUT_REDIRECT;
            info->out_file = parameters[i+1];
            parameters[i] = NULL;
            i+=2;
        }
        else if(strcmp(parameters[i],">>")==0)
        {
            info->flag |= OUT_REDIRECT_APPEND;
            info->out_file = parameters[i+1];
            parameters[i] = NULL;
            i+=2;
        }
        else if(strcmp(parameters[i],"|")==0)
        {
            char* pCh;
            info->flag |= IS_PIPED;
            parameters[i] = NULL;
            info->command2 = parameters[i+1];
            info->parameters2 = &parameters[i+1];
            for(pCh = info->parameters2[0]+strlen(info->parameters2[0]);
                    pCh!=&(info->parameters2[0][0]) && *pCh!='/';pCh--)
                ;
            if(*pCh == '/')
                pCh++;
            info->parameters2[0] = pCh;
            break;
        }
        else
            i++;
    }
    return 1;
}


/*
	main process
*/
int main()
{
	char prompt[MAXLEN];
	

	while (1)
	{
		int pipe_fd[2],in_fd,out_fd;		
		typePrompt((char*)prompt);
		printf("%s", prompt);		

		char *cmd = NULL;
		char **para;
		para = malloc(sizeof(char *)*(MAXLEN+2));

		char buffer[MAXLEN];
		gets(buffer);
		int paraNum = getCmdPara((char *)buffer, &cmd, para);
		paraNum--;
		struct parse_info info;
		parsing(para, paraNum, &info);

		if (para==-1 || builtInCmd(cmd, para))
			continue;

		pid_t pid;
		pid_t ChdPid;
		pid_t ChdPid2;
		int status;

	 	if(info.flag & IS_PIPED) //command2 is not null
	        {                
	            if(pipe(pipe_fd)<0)
	            {
	                printf("Wshell error:pipe failed.\n");
	                exit(0);
	            }
	        } 

		if((ChdPid = fork())!=0) //wshell
	        {
	            if(info.flag & IS_PIPED)
	            {
	                if((ChdPid2=fork()) == 0) //command2
	                {
	                    close(pipe_fd[1]);
	                    close(fileno(stdin)); 
	                    dup2(pipe_fd[0], fileno(stdin));
	                    close(pipe_fd[0]); 
	                    execvp(info.command2,info.parameters2);
	                }
	                else
	                {
	                    close(pipe_fd[0]);
	                    close(pipe_fd[1]);
	                    waitpid(ChdPid2,&status,0); //wait command2
	                }
	            }          
	            waitpid(ChdPid,&status,0);//wait command1

	        }
	        else //command1
	        {
				
	            if(info.flag & IS_PIPED) //command2 is not null
	            {                
	                if(!(info.flag & OUT_REDIRECT) && !(info.flag & OUT_REDIRECT_APPEND)) // ONLY PIPED
		           {
	                    close(pipe_fd[0]);
	                    close(fileno(stdout)); 
	                    dup2(pipe_fd[1], fileno(stdout));
	                    close(pipe_fd[1]);
	                }
	                else //OUT_REDIRECT and PIPED
		           {
	                    close(pipe_fd[0]);
	                    close(pipe_fd[1]);//send a EOF to command2
	                    if(info.flag & OUT_REDIRECT)
	    	               out_fd = open(info.out_file, O_WRONLY|O_CREAT|O_TRUNC, 0666);
	                    else
	    	               out_fd = open(info.out_file, O_WRONLY|O_APPEND|O_TRUNC, 0666);
	                    close(fileno(stdout)); 
	                    dup2(out_fd, fileno(stdout));
	                    close(out_fd);	        
	                }
	            }
	            else
	            {
	                if(info.flag & OUT_REDIRECT) // OUT_REDIRECT WITHOUT PIPE
		           {
	                    out_fd = open(info.out_file, O_WRONLY|O_CREAT|O_TRUNC, 0666);
	                    close(fileno(stdout)); 
	                    dup2(out_fd, fileno(stdout));
	                    close(out_fd);
	                }
	                if(info.flag & OUT_REDIRECT_APPEND) // OUT_REDIRECT_APPEND WITHOUT PIPE
		           {
	                    out_fd = open(info.out_file, O_WRONLY|O_CREAT|O_APPEND, 0666);
	                    close(fileno(stdout)); 
	                    dup2(out_fd, fileno(stdout));
	                    close(out_fd);
	                }
	            }
	            
	            if(info.flag & IN_REDIRECT)
	            {
	                in_fd = open(info.in_file, O_CREAT |O_RDONLY, 0666);
	                close(fileno(stdin)); 
	                dup2(in_fd, fileno(stdin));
	                close(in_fd); 
	            }
	            if (strcmp("ll", cmd)==0)
		    {
		    	    sprintf(buffer, "%s", "ls -l");
			    getCmdPara(buffer, &cmd, para);
			    execvp("ls", para);
		    }
		    else
		    {
			    execvp(cmd, para);
		    }
	        }
	}
	return 0;
}

